﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Xml.XPath;

namespace InfoPathHelpers.Xml
{
    public static class XmlHelpers
    {
        /// <summary>
        /// Gets the processing instruction with the supplied name.
        /// </summary>
        /// <param name="root">The root of the document from which to get the processing instruction.</param>
        /// <param name="instructionName">The name of the processing instruction.</param>
        /// <returns>An XPathNavigator pointing to the processing instruction if it is found, otherwise null.</returns>
        public static XPathNavigator GetProcessingInstruction(XPathNavigator root, string instructionName)
        {
            if (root == null) throw new ArgumentNullException("root");

            return root.SelectSingleNode("processing-instruction('" + instructionName + "')");
        }

        /// <summary>
        /// Gets the attibutes of the processing instruction supplied.
        /// </summary>
        /// <param name="processingInstruction">An XPathNavigator pointing to the processing instruction.</param>
        /// <returns>A dictionary in which the Key is the name of the attribute and the value is the value of the attibute.</returns>
        public static Dictionary<string, string> GetProcessingInstructionAttributes(XPathNavigator processingInstruction)
        {
            if (processingInstruction == null) throw new ArgumentNullException("processingInstruction");

            var Attributes = new Dictionary<string, string>();

            foreach(string Attribute in processingInstruction.Value.Split(' '))
            {
                var AttributeParts = Attribute.Split('=');
                if (AttributeParts.Length == 2)
                {
                    Attributes.Add(AttributeParts[0], AttributeParts[1].Trim('"'));
                }
            }

            return Attributes;
        }

        /// <summary>
        /// Gets the attibutes of the processing instruction with the supplied name.
        /// </summary>
        /// <param name="root">The root of the document from which to get the processing instruction.</param>
        /// <param name="instructionName">The name of the processing instruction.</param>
        /// <returns>A dictionary in which the Key is the name of the attribute and the value is the value of the attibute.</returns>
        public static Dictionary<string, string> GetProcessingInstructionAttributes(XPathNavigator root, string instructionName)
        {
            var ProcssingInstruction = XmlHelpers.GetProcessingInstruction(root, instructionName);

            return GetProcessingInstructionAttributes(ProcssingInstruction);
        }

        /// <summary>
        /// Gets the value of the specified attibute and processing instruction.
        /// </summary>
        /// <param name="root">The root of the document from which to get the processing instruction attribute.</param>
        /// <param name="instructionName">The name of the processing instruction.</param>
        /// <param name="attributeName">The name of the attibute for which to get the value.</param>
        /// <returns>The value of the processing instruction if it exists; otherwise an empty string.</returns>
        public static string GetProcessingInstructionAttribute(XPathNavigator root, string instructionName, string attributeName)
        {
            var Attribute = string.Empty;
            var ProcssingInstruction = XmlHelpers.GetProcessingInstruction(root, instructionName);
            var Attributes = GetProcessingInstructionAttributes(ProcssingInstruction);
            if(Attributes.ContainsKey(attributeName))
            {
                Attribute = Attributes[attributeName];
            }
            return Attribute;
        }

        /// <summary>
        /// Sets the value of the specified attibute and processing instruction if it exists or adds it.
        /// </summary>
        /// <param name="root">The root of the document from which to set the processing instruction attribute.</param>
        /// <param name="instructionName">The name of the processing instruction.</param>
        /// <param name="attributeName">The name of the attibute for which to set the value.</param>
        public static void SetProcessingInstructionAttribute(XPathNavigator root, string instructionName, string attributeName, string value)
        {
            var ProcessingInstruction = XmlHelpers.GetProcessingInstruction(root, instructionName);
            var Attributes = GetProcessingInstructionAttributes(ProcessingInstruction);
            if (Attributes.ContainsKey(attributeName))
            {
                Attributes[attributeName] = value;
            }
            else
            {
                Attributes.Add(attributeName, value);
            }
            ProcessingInstruction.SetValue(BuildProcessingInstructionValue(Attributes));
        }

        private static string BuildProcessingInstructionValue(Dictionary<string,string> instructionAttributes)
        {
            var ProcessingInstruction = string.Empty;

            foreach (var attribute in instructionAttributes)
            {
                // Add each attribute using the Key as the attribute name and Value as the attribute value.
                ProcessingInstruction += string.Format(" {0}=\"{1}\"", attribute.Key, attribute.Value);
            }

            return ProcessingInstruction;
        }

        /// <summary>
        /// Gets the full XPath expression of the supplied <paramref name="node"/>.
        /// </summary>
        /// <param name="node">The node for which to get the XPath.</param>
        /// <returns>The full XPath expression for the supplied <paramref name="node"/>.</returns>
        public static string GetFullXPath(XPathNavigator node)
        {
            // Clone the node so we don't mess with the position of the one passed in.
            node = node.Clone();
            string XPath = string.Empty;

            do
            {

                XPath = string.Format("{0}[{1}]/{2}", node.Name, GetElementIndex(node), XPath);
                node.MoveToParent();
            }
            // Keep moving to the parent node and prepending it's name
            // to the XPath until the root is reached.
            while (node.NodeType != XPathNodeType.Root);
            return XPath.TrimEnd('/');
        }

        public static int GetElementIndex(XPathNavigator element)
        {
            var Expression = string.Format("count(preceding-sibling::{0})", element.Name);
            var NamespaceResolver = new XmlNamespaceManager(element.NameTable);
            NamespaceResolver.AddNamespace(element.Prefix, element.NamespaceURI);
            int Count = Convert.ToInt32(element.Evaluate(Expression, NamespaceResolver));
            return Count + 1;
        }
    }
}
